package com;

import java.lang.instrument.Instrumentation;

/**
 * 实现agent启动方法
 * Java Agent支持目标JVM启动时加载，也支持在目标JVM运行时加载，这两种不同的加载模式会使用不同的入口函数，
 * 如果需要在目标JVM启动的同时加载Agent，那么可以选择实现下面的方法：
 *
 * [1] public static void premain(String agentArgs, Instrumentation inst);
 * [2] public static void premain(String agentArgs);
 * JVM将首先寻找[1]，如果没有发现[1]，再寻找[2]。如果希望在目标JVM运行时加载Agent，则需要实现下面的方法：
 *
 * [1] public static void agentmain(String agentArgs, Instrumentation inst);
 * [2] public static void agentmain(String agentArgs);
 * 这两组方法的第一个参数AgentArgs是随同 “–javaagent”一起传入的程序参数，如果这个字符串代表了多个参数，就需要自己解析这些参数。
 * inst是Instrumentation类型的对象，是JVM自动传入的，我们可以拿这个参数进行类增强等操作。
 * JavaAgent 是JDK 1.5 以后引入的，也可以叫做Java代理。
 *
 * 指定Main-Class
 * Agent需要打包成一个jar包，在ManiFest属性中指定“Premain-Class”或者“Agent-Class”,且需根据需求定义Can-Redefine-Classes和Can-Retransform-Classes：
 *
 * Manifest-Version: 1.0
 * preMain-Class: com.test.AgentClass
 * Archiver-Version: Plexus Archiver
 * Agent-Class: com.test.AgentClass
 * Can-Redefine-Classes: true
 * Can-Retransform-Classes: true
 * Created-By: Apache Maven 3.3.9
 * Build-Jdk: 1.8.0_112
 *
 * @author wangzhilong
 * @date 2020/4/14
 */
public class AgentMain {

    /**
     * 该方法在main方法之前运行，与main方法运行在同一个JVM中
     * 并被同一个System ClassLoader装载
     * 被统一的安全策略(security policy)和上下文(context)管理
     */
    public static void premain(String agentOps, Instrumentation inst){

        System.out.println("====代理 方法执行");
        System.out.println(agentOps);
        //添加transformer
        inst.addTransformer(new MyTransformer());
    }


    /**
     * 如果不存在 premain(String agentOps, Instrumentation inst)
     * 则会执行 premain(String agentOps)
     */
    public static void premain(String agentOps){

        System.out.println("====代理方法执行2====");
        System.out.println(agentOps);
    }
    public static void main(String[] args) {
        // TODO Auto-generated method stub

    }

    /**
     * JDK1.6之后提供的在服务启动之后执行agent程序。
     * premain是Java SE5开始就提供的代理方式，由于其必须在命令行指定代理jar，并且代理类必须在main方法前启动。
     * 因此，要求开发者在应用前就必须确认代理的处理逻辑和参数内容等等。在有些场合下，premain代理方式不能满足需求。
     * 为解决运行时启动代理类的问题，Java SE6开始提供了在应用程序的VM启动后在动态添加代理的方式，即agentmain方式
     * 需要在MANIFEST.MF 文件中添加 Agent-Class: xxxx.agent  主函数类
     *
     * 由于agent main方式无法向pre main方式那样在命令行指定代理jar，因此需要借助Attach Tools  API。
     * 使用com.sun.tools.attach包下的VirtualMachine类，使用attach pid 来得到相应的VirtumalMachine，使用loadAgent 方法指定AgentMain所在类并加载。其中
     * com.sun.tools.attach.VirtualMachine的jar包是jdk下lib中的tools.jar
     * 需要以前两行代码给某一个启动类附加agent程序
     * final VirtualMachine vm = VirtualMachine.attach("43846");
     * vm.loadAgent("/Users/wangdigua/IdeaProjects/spring-boot-samples/java-agent/target/java-agent-1.0.jar");
     * @param args
     * @param inst
     */
    public static void agentmain(String args, Instrumentation inst) {
        System.out.println("loadagent after main run.args=" + args);
        final Class[] classes = inst.getAllLoadedClasses();
        for (Class cls : classes) {
            System.out.println(cls.getName());
        }
        System.out.println("agent run completely.");
    }


}
